/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// sys_win.c -- Win32 system interface code

#include "quakedef.h"
#include "winquake.h"
#include "errno.h"
#include "resource.h"
#include "conproc.h"
#include <direct.h>
#include <time.h>
#include <io.h> //Any Pak File - Eradicator

#define MINIMUM_WIN_MEMORY		0x4000000 //and 64 meg minimum
#define MAXIMUM_WIN_MEMORY		0x40000000 //PENTA: Allow quake to allocate 1 gig at max

#define CONSOLE_ERROR_TIMEOUT	60.0	// # of seconds to wait on Sys_Error running
//  dedicated before exiting
#define PAUSE_SLEEP		50				// sleep time on pause or minimization
#define NOT_FOCUS_SLEEP	20				// sleep time when not focus

int			starttime;
qboolean	ActiveApp, Minimized;
qboolean	WinNT;

static double		pfreq;
static double		curtime = 0.0;
static double		lastcurtime = 0.0;
static int			lowshift;
qboolean			isDedicated;
static qboolean		sc_return_on_enter = false;
HANDLE				hinput, houtput;

static HANDLE	tevent;
static HANDLE	hFile;
static HANDLE	heventParent;
static HANDLE	heventChild;

void Sys_InitFloatTime (void);

volatile int					sys_checksum;


/*
================
Sys_PageIn
================
*/
void Sys_PageIn (void *ptr, int size)
{
    byte	*x;
    int		m, n;

// touch all the memory to make sure it's there. The 16-page skip is to
// keep Win 95 from thinking we're trying to page ourselves in (we are
// doing that, of course, but there's no reason we shouldn't)
    x = (byte *)ptr;

    for (n=0 ; n<4 ; n++)
    {
        for (m=0 ; m<(size - 16 * 0x1000) ; m += 4)
        {
            sys_checksum += *(int *)&x[m];
            sys_checksum += *(int *)&x[m + 16 * 0x1000];
        }
    }
}


/*
===============================================================================

FILE IO

===============================================================================
*/

#define	MAX_HANDLES		256 //Changed max pak files to 256 from 10 - Eradicator
FILE	*sys_handles[MAX_HANDLES];

int		Sys_FindHandle (void)
{
    int		i;

    for (i=1 ; i<MAX_HANDLES ; i++)
        if (!sys_handles[i])
            return i;
    Sys_Error ("out of handles");
    return -1;
}

/*
================
lengthoffile //Renamed from filelength to stop conflict - Eradicator
================
*/
int lengthoffile (FILE *f)
{
    int		pos;
    int		end;

    pos = ftell (f);
    fseek (f, 0, SEEK_END);
    end = ftell (f);
    fseek (f, pos, SEEK_SET);

    return end;
}

int Sys_FileOpenRead (char *path, int *hndl)
{
    FILE	*f;
    int		i, retval;

    i = Sys_FindHandle ();

    f = fopen(path, "rb");

    if (!f)
    {
        *hndl = -1;
        retval = -1;
    }
    else
    {
        sys_handles[i] = f;
        *hndl = i;
        retval = lengthoffile(f);
    }
    return retval;
}

int Sys_FileOpenWrite (char *path)
{
    FILE	*f;
    int		i;

    i = Sys_FindHandle ();

    f = fopen(path, "wb");

    if (!f)
    {
        Sys_Error ("Error opening %s: %s", path, strerror(errno));
    }
    sys_handles[i] = f;

    return i;
}

void Sys_FileClose (int handle)
{
    fclose (sys_handles[handle]);
    sys_handles[handle] = NULL;
}

void Sys_FileSeek (int handle, int position)
{
    fseek (sys_handles[handle], position, SEEK_SET);
}

int Sys_FileRead (int handle, void *dest, int count)
{
    int	x;
    x = fread (dest, 1, count, sys_handles[handle]);
    return x;
}

int Sys_FileWrite (int handle, void *data, int count)
{
    int		x;
    x = fwrite (data, 1, count, sys_handles[handle]);
    return x;
}

int	Sys_FileTime (char *path)
{
    FILE	*f;
    int		retval;

    f = fopen(path, "rb");

    if (f)
    {
        fclose(f);
        retval = 1;
    }
    else
    {
        retval = -1;
    }
    return retval;
}

void Sys_mkdir (char *path)
{
    _mkdir (path);
}


void Sys_Strtime(char *buf)
{
    _strtime(buf);
}

// directory entry list internal data
typedef struct
{
    struct _finddata_t  fileinfo;
    int                 handle;
    char                dir[MAX_OSPATH];
} windirdata_t;


dirdata_t *Sys_Findfirst (char *dir, char *filter, dirdata_t *dirdata)
{
    windirdata_t    *windata;
    char            dirfilter[MAX_OSPATH];

    if (dirdata)
    {
        windata=Z_Malloc (sizeof(windirdata_t));
        sprintf(dirfilter,"%s/%s", dir, filter);
        windata->handle = _findfirst (dirfilter, &windata->fileinfo);

        if (windata->handle!=-1)
        {
            strncpy(windata->dir, dir, MAX_OSPATH);
            sprintf(dirdata->entry,"%s/%s", dir, windata->fileinfo.name);
            dirdata->internal = windata;
            return dirdata;
        }
    }
    return NULL;
}

dirdata_t *Sys_Findnext (dirdata_t *dirdata)
{
    windirdata_t *windata;

    if (dirdata)
    {
        windata=dirdata->internal;

        // next entry ?
        if (_findnext( windata->handle, &(windata->fileinfo))!=-1)
        {
            sprintf(dirdata->entry,"%s/%s", windata->dir, windata->fileinfo.name);
            return dirdata;
        }

        // no -> close
        _findclose(windata->handle);
        Z_Free (windata);
        dirdata->internal=NULL;
    }
    return NULL;
}

void Sys_Findclose (dirdata_t *dirdata)
{
    windirdata_t *windata;

    if (dirdata)
    {
        windata = dirdata->internal;

        if (windata)
        {
            _findclose(windata->handle);
            Z_Free (windata);
            dirdata->internal=NULL;
        }
    }
}

/*
===============================================================================

SYSTEM IO

===============================================================================
*/

/*
================
Sys_MakeCodeWriteable
================
*/
void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length)
{
    DWORD  flOldProtect;

    if (!VirtualProtect((LPVOID)startaddr, length, PAGE_READWRITE, &flOldProtect))
    {
        Sys_Error("Protection change failed\n");
    }
}

/*
================
Sys_Init
================
*/
void Sys_Init (void)
{
    LARGE_INTEGER	PerformanceFreq;
    unsigned int	lowpart, highpart;
    OSVERSIONINFO	vinfo;

    if (!QueryPerformanceFrequency (&PerformanceFreq))
    {
        Sys_Error ("No hardware timer available");
    }

// get 32 out of the 64 time bits such that we have around
// 1 microsecond resolution
    lowpart = (unsigned int)PerformanceFreq.LowPart;
    highpart = (unsigned int)PerformanceFreq.HighPart;
    lowshift = 0;

    while (highpart || (lowpart > 2000000.0))
    {
        lowshift++;
        lowpart >>= 1;
        lowpart |= (highpart & 1) << 31;
        highpart >>= 1;
    }
    pfreq = 1.0 / (double)lowpart;

    Sys_InitFloatTime ();

    vinfo.dwOSVersionInfoSize = sizeof(vinfo);

    if (!GetVersionEx (&vinfo))
    {
        Sys_Error ("Couldn't get OS info");
    }

    if ((vinfo.dwMajorVersion < 4) || (vinfo.dwPlatformId == VER_PLATFORM_WIN32s))
    {
        Sys_Error ("Tenebrae requires at least Win95 or NT 4.0");
    }

    if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        WinNT = true;
    }
    else
    {
        WinNT = false;
    }
}

void Sys_Error (char *error, ...)
{
    va_list		argptr;
    char		text[1024], text2[1024];
    char		*text3 = "Press Enter to exit\n";
    char		*text4 = "***********************************\n";
    char		*text5 = "\n";
    DWORD		dummy;
    double		errortime;
    static int	in_sys_error0 = 0;
    static int	in_sys_error1 = 0;
    static int	in_sys_error2 = 0;
    static int	in_sys_error3 = 0;

    if (!in_sys_error3)
    {
        in_sys_error3 = 1;
    }
    va_start (argptr, error);
    va_vsnprintf (text, sizeof(text), error, argptr);
    va_end (argptr);

    //PENTA: Write sys_error's also to the log file
    Con_DebugLog(va("%s/qconsole.log", com_gamedir), "Sys_Error: %s", text);

    if (isDedicated)
    {
        va_start (argptr, error);
        va_vsnprintf (text, sizeof(text), error, argptr);
        va_end (argptr);

        va_snprintf (text2, sizeof(text2), "ERROR: %s\n", text);
        WriteFile (houtput, text5, strlen (text5), &dummy, NULL);
        WriteFile (houtput, text4, strlen (text4), &dummy, NULL);
        WriteFile (houtput, text2, strlen (text2), &dummy, NULL);
        WriteFile (houtput, text3, strlen (text3), &dummy, NULL);
        WriteFile (houtput, text4, strlen (text4), &dummy, NULL);

        errortime = Sys_FloatTime ();
        sc_return_on_enter = true;	// so Enter will get us out of here

        while (!Sys_ConsoleInput () && ((Sys_FloatTime () - errortime) < CONSOLE_ERROR_TIMEOUT))
        {
        }
    }
    else
    {
        // switch to windowed so the message box is visible, unless we already
        // tried that and failed
        if (!in_sys_error0)
        {
            in_sys_error0 = 1;
            VID_SetDefaultMode ();
            MessageBox(NULL, text, "Quake Error", MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
        }
        else
        {
            MessageBox(NULL, text, "Double Quake Error", MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
        }
    }

    if (!in_sys_error1)
    {
        in_sys_error1 = 1;
        Host_Shutdown ();
    }

// shut down QHOST hooks if necessary
    if (!in_sys_error2)
    {
        in_sys_error2 = 1;
        DeinitConProc ();
    }
    exit (1);
}

void Sys_Printf (char *fmt, ...)
{
    va_list		argptr;
    char		text[1024];
    DWORD		dummy;

    if (isDedicated)
    {
        va_start (argptr,fmt);
        va_vsnprintf (text, sizeof(text), fmt, argptr);
        va_end (argptr);

        WriteFile(houtput, text, strlen (text), &dummy, NULL);
    }
}

void Sys_Quit (void)
{
    Host_Shutdown();

    if (tevent)
    {
        CloseHandle (tevent);
    }

    if (isDedicated)
    {
        FreeConsole ();
    }

// shut down QHOST hooks if necessary
    DeinitConProc ();

    exit (0);
}


/*
================
Sys_FloatTime
================
*/
double Sys_FloatTime (void)
{
   static qboolean firsttime = true;
   static __int64 qpcfreq = 0;
   static __int64 currqpccount = 0;
   static __int64 lastqpccount = 0;
   static double qpcfudge = 0;
   DWORD currtime = 0;
   static DWORD lasttime = 0;
   static DWORD inittime = 0;

   if (firsttime)
   {
      timeBeginPeriod (1);
      inittime = lasttime = timeGetTime ();
      QueryPerformanceFrequency ((LARGE_INTEGER *) &qpcfreq);
      QueryPerformanceCounter ((LARGE_INTEGER *) &lastqpccount);
      firsttime = false;
      return 0;
   }

   // get the current time from both counters
   currtime = timeGetTime ();
   QueryPerformanceCounter ((LARGE_INTEGER *) &currqpccount);

   if (currtime != lasttime)
   {
      // requery the frequency in case it changes (which it can on multicore machines)
      QueryPerformanceFrequency ((LARGE_INTEGER *) &qpcfreq);

      // store back times and calc a fudge factor as timeGetTime can overshoot on a sub-millisecond scale
      qpcfudge = ((double) (currqpccount - lastqpccount) / (double) qpcfreq) - ((double) (currtime - lasttime) * 0.001);
      lastqpccount = currqpccount;
      lasttime = currtime;
   }
   else qpcfudge = 0;

   // the final time is the base from timeGetTime plus an addition from QPC
   return ((double) (currtime - inittime) * 0.001) + ((double) (currqpccount - lastqpccount) / (double) qpcfreq) + qpcfudge;
}

/*
================
Sys_InitFloatTime
================
*/
void Sys_InitFloatTime (void)
{
    int		j;

    Sys_FloatTime ();

    j = COM_CheckParm("-starttime");

    if (j)
    {
        curtime = (double) (Q_atof(com_argv[j+1]));
    }
    else
    {
        curtime = 0.0;
    }
    lastcurtime = curtime;
}

char *Sys_ConsoleInput (void)
{
    static char	    text[256];
    static int		len;
    INPUT_RECORD	recs[1024];
    int		        dummy;
    int		        ch, numread, numevents;

    if (!isDedicated)
    {
        return NULL;
    }

    for ( ;; )
    {
        if (!GetNumberOfConsoleInputEvents (hinput, (LPDWORD)&numevents))
        {
            Sys_Error ("Error getting # of console events");
        }

        if (numevents <= 0) break;

        if (!ReadConsoleInput(hinput, recs, 1, (LPDWORD)&numread))
        {
            Sys_Error ("Error reading console input");
        }

        if (numread != 1)
        {
            Sys_Error ("Couldn't read console input");
        }

        if (recs[0].EventType == KEY_EVENT)
        {
            if (!recs[0].Event.KeyEvent.bKeyDown)
            {
                ch = recs[0].Event.KeyEvent.uChar.AsciiChar;

                switch (ch)
                {
                case '\r':
                    WriteFile(houtput, "\r\n", 2, (LPDWORD)&dummy, NULL);

                    if (len)
                    {
                        text[len] = 0;
                        len = 0;
                        return text;
                    }
                    else if (sc_return_on_enter)
                    {
                        // special case to allow exiting from the error handler on Enter
                        text[0] = '\r';
                        len = 0;
                        return text;
                    }

                    break;

                case '\b':
                    WriteFile(houtput, "\b \b", 3, (LPDWORD)&dummy, NULL);
                    if (len)
                    {
                        len--;
                    }
                    break;

                default:
                    if (ch >= ' ')
                    {
                        WriteFile(houtput, &ch, 1, (LPDWORD)&dummy, NULL);
                        text[len] = ch;
                        len = (len + 1) & 0xff;
                    }
                    break;

                }
            }
        }
    }
    return NULL;
}

void Sys_Sleep (void)
{
    Sleep (1);
}

void Sys_SendKeyEvents (void)
{
    MSG        msg;

    while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
    {
        // we always update if there are any event, even if we're paused
        scr_skipupdate = 0;

        if (!GetMessage (&msg, NULL, 0, 0))
        {
            Sys_Quit ();
        }
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
}

/*
==============================================================================

 WINDOWS CRAP

==============================================================================
*/

/*
==================
WinMain
==================
*/
void SleepUntilInput (int time)
{
    MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT);
}


/*
==================
WinMain
==================
*/
HINSTANCE	global_hInstance;
int			global_nCmdShow;
char		*argv[MAX_NUM_ARGVS];
static char	*empty_string = "";
HWND		hwnd_dialog;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    quakeparms_t	parms;
    double			time, oldtime, newtime;
    MEMORYSTATUS	lpBuffer;
    static	char	cwd[1024];
    int				j,t;
    int				mb_mem_size=50;
    RECT			rect;

    /* previous instances do not exist in Win32 */
    if (hPrevInstance)
        return 0;

    global_hInstance = hInstance;
    global_nCmdShow = nCmdShow;

    lpBuffer.dwLength = sizeof(MEMORYSTATUS);
    GlobalMemoryStatus (&lpBuffer);

    if (!GetCurrentDirectory (sizeof(cwd), cwd))
        Sys_Error ("Couldn't determine current directory");

    if (cwd[Q_strlen(cwd)-1] == '/')
        cwd[Q_strlen(cwd)-1] = 0;

    parms.basedir = cwd;
    parms.cachedir = NULL;

    parms.argc = 1;
    argv[0] = empty_string;

    while (*lpCmdLine && (parms.argc < MAX_NUM_ARGVS))
    {
        while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
            lpCmdLine++;

        if (*lpCmdLine)
        {
            argv[parms.argc] = lpCmdLine;
            parms.argc++;

            while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
                lpCmdLine++;

            if (*lpCmdLine)
            {
                *lpCmdLine = 0;
                lpCmdLine++;
            }

        }
    }

    parms.argv = argv;

    COM_InitArgv (parms.argc, parms.argv);

    parms.argc = com_argc;
    parms.argv = com_argv;

    isDedicated = (COM_CheckParm ("-dedicated") != 0);

    if (!isDedicated)
    {
        hwnd_dialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, NULL);

        if (hwnd_dialog)
        {
            if (GetWindowRect (hwnd_dialog, &rect))
            {
                if (rect.left > (rect.top * 2))
                {
                    SetWindowPos (hwnd_dialog, 0,
                                  (rect.left / 2) - ((rect.right - rect.left) / 2),
                                  rect.top, 0, 0,
                                  SWP_NOZORDER | SWP_NOSIZE);
                }
            }

            ShowWindow (hwnd_dialog, SW_SHOWDEFAULT);
            UpdateWindow (hwnd_dialog);
            SetForegroundWindow (hwnd_dialog);
        }
    }

// take the greater of all the available memory or half the total memory,
// but at least 64 Mb and no more than 1 Gb, unless they explicitly
// request otherwise
    parms.memsize = lpBuffer.dwAvailPhys;


    //on some systems dwAvailPhys seems to return all physical mem
    //not only the avail..
    if (lpBuffer.dwAvailPhys >= (size_t)(lpBuffer.dwTotalPhys * 0.9))
    {
        parms.memsize = (int)(lpBuffer.dwTotalPhys*0.75);
    }

    if (parms.memsize < MINIMUM_WIN_MEMORY)
	{
        parms.memsize = MINIMUM_WIN_MEMORY;
	}

    if (parms.memsize < (int) (lpBuffer.dwTotalPhys >> 1))
	{
        parms.memsize = (int) (lpBuffer.dwTotalPhys >> 1);
	}

    if (parms.memsize > MAXIMUM_WIN_MEMORY)
	{
        parms.memsize = MAXIMUM_WIN_MEMORY;
	}
    j = COM_CheckParm ("-heapsize");

    if (j)
	{
        mb_mem_size = (int) (Q_atof (com_argv[j + 1]));
	}
    parms.memsize = mb_mem_size * 1024 * 1024;
    parms.membase = malloc (parms.memsize);

    // not enough memory !!
    if (!parms.membase)
    {
        Sys_Error("Not enough memory - asked for %d - change with -heapsize <value in Mb> - check disk space\n",mb_mem_size);
    }
    Sys_PageIn (parms.membase, parms.memsize);

    tevent = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (!tevent)
	{
        Sys_Error ("Couldn't create event\n");
	}

    if (isDedicated)
    {
        if (!AllocConsole ())
        {
            Sys_Error ("Couldn't create dedicated server console\n");
        }
        hinput = GetStdHandle (STD_INPUT_HANDLE);
        houtput = GetStdHandle (STD_OUTPUT_HANDLE);

        // give QHOST a chance to hook into the console
        if ((t = COM_CheckParm ("-HFILE")) > 0)
        {
            if (t < com_argc)
			{
                hFile = (HANDLE)Q_atoi (com_argv[t+1]);
			}
        }

        if ((t = COM_CheckParm ("-HPARENT")) > 0)
        {
            if (t < com_argc)
			{
                heventParent = (HANDLE)Q_atoi (com_argv[t+1]);
			}
        }

        if ((t = COM_CheckParm ("-HCHILD")) > 0)
        {
            if (t < com_argc)
			{
                heventChild = (HANDLE)Q_atoi (com_argv[t+1]);
			}
        }
        InitConProc (hFile, heventParent, heventChild);
    }
    Sys_Init ();

// because sound is off until we become active
    S_BlockSound ();

    Sys_Printf ("Host_Init\n");
    Host_Init (&parms);

    oldtime = Sys_FloatTime ();

    /* main window message loop */
    while (1)
    {
        if (isDedicated)
        {
            newtime = Sys_FloatTime ();
            time = newtime - oldtime;

            while (time < sys_ticrate.value )
            {
                Sys_Sleep();
                newtime = Sys_FloatTime ();
                time = newtime - oldtime;
            }
        }
        else
        {
            // yield the CPU for a little while when paused, minimized, or not the focus
            if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing)
            {
                SleepUntilInput (PAUSE_SLEEP);
                scr_skipupdate = 1;		// no point in bothering to draw
            }
            else if (!ActiveApp && !DDActive)
            {
                SleepUntilInput (NOT_FOCUS_SLEEP);
            }

            newtime = Sys_FloatTime ();
            time = newtime - oldtime;
        }

        Host_Frame (time);
        oldtime = newtime;
    }

    /* return success of application */
    return TRUE;
}

